package ngat.oss;

import ngat.message.OSS.*;
import ngat.util.logging.*;
import ngat.phase2.*; 
import ngat.instrument.*;
import ngat.phase2.nonpersist.*;

import java.io.*;
import java.util.*;
import java.text.*;

import jyd.dbms.*;
import jyd.storable.*;
import jyd.collection.*;

/**
 * Client: RCS.
 * Action: Sent by RCS when a Group's observations have been successfully completed. 
 * The Group's <i>done</i> flag is set and the <i>doneDate</i> is set to the current date.
 * @author S.N.Fraser
 */

public class UPDATE_GROUPImpl extends TransactionImpl {
    
    /** The path used to locate the Group in the OSS database. */
    protected Path groupPath;

    /** Time when this Group was completed (or failed).*/
    protected long doneTime;

    /** A key to unlock the record.
     * @deprecated Not used since 20 nov 2003.
     */
    protected int  key;

    /** True if this Group was executed successfully.*/
    protected boolean success;

    /** Some details of the execution filled in by the RCS.*/
    protected String details;

    /** A code to indicate the reason for aborting (if it failed), n/a if successful.*/
    protected int code;

    /** Time used in executing this Group (secs).*/
    protected long usedTime;

    /** Time this group should be considered Taboo if it failed for some systemic reason.*/
    protected long tabuTime;
   
    /** Create an UPDATE_GROUPImpl for the supplied request.*/
    public UPDATE_GROUPImpl(UPDATE_GROUP request) {
	super(request);
	this.groupPath = request.getGroupPath();
	this.doneTime  = request.getDoneTime();
	this.key       = request.getKey();
	this.success   = request.getSuccess();
	this.details   = request.getDetails();
	this.usedTime  = request.getUsedTime();
	this.code      = request.getCode();
	this.tabuTime  = request.getTabuTime();
    }

    /**
     * Updates the Group's done fields.
     * @return Boolean description of operation or an OSS_Error.
     */
    public TRANSACTION_DONE exec() {
	myLog = LogManager.getLogger("SCHEDULE");

	NumberFormat nf = NumberFormat.getInstance();
	nf.setMaximumFractionDigits(2);

	long now  = System.currentTimeMillis();
	
	Transaction t = null;
	try {
	    t = Transaction.current();

	    
	    logBuffer = new StringBuffer("Updating Group: "+groupPath.toString()+
					 "\n\tUsing key:      "+key);
	    if (success) {
		logBuffer.append("\n\tCompleted at:"+sdf.format(new Date(doneTime))+	 		
				 "\n\t Time used:     "+(usedTime/1000.0)+" secs."+
				 "\n\t Info:          "+details);
	    } else {
		logBuffer.append("\n\tFailed at:      "+sdf.format(new Date(doneTime))+				
				 "\n\t Abort Code:    "+code+ 
				 "\n\t Time used:     "+(usedTime/1000.0)+" secs."+
				 "\n\t Info:          "+details+
				 "\n\t Tabu until:    "+sdf.format(new Date(now+tabuTime)));
	    }
	    
	    logBuffer.append("\nSearching for  Proposal:");
	    
	    Proposal proposal = null;
	    try {
		proposal = OSS_Utilities.fetchProposal(groupPath);
	    } catch (IOException iox) {
		logger.log(1, CLASS, id, "exec",
			   "Proposal: "+groupPath.getProposalByName()+
			   " Error fetching from Database: "+iox);
		return error(TRANSACTION.DATABASE_READ_ERROR,			   
				     "Proposal: "+groupPath.getProposalByName()+
				     " Error reading from database: "+iox);
	    }		
	    	    
	    // Check it exists.
	    if (proposal == null) {
		logger.log(1, CLASS, id, "exec",
			   "Proposal: "+groupPath.getProposalByName()+" Not in database.");
		return error(TRANSACTION.GROUP_UPDATE_ERROR, 
			     "Could not find Proposal: "+groupPath.getProposalByName());
	    } 
	    
	    logBuffer.append("\nFetched Proposal: "+proposal.getName());
	    
	    Group group = proposal.findGroup(groupPath.getGroupByName());
	    
	    // Check it exists.
	    if (group == null) {
		logger.log(1, CLASS, id, "exec",
			   "Group: "+groupPath.toString()+" Not in database.");
		return error(TRANSACTION.GROUP_UPDATE_ERROR, 
			     "Could not find Group: "+groupPath.toString());
	    }
	    
	    logBuffer.append("\nFound Group:");
	    
	    // Always Unlock it.
	    group.unLock(key);
	    //proposal.unLock(key);
	    proposal.forceUnLock();
	    proposal.setLastUnLocked(now);

	    logBuffer.append("\nUnlocking: Group and Proposal (ForcedUnlock))");
	    
	    // Notify scheduling info that current group is done
	    Scheduling.setCurrentGroup(null);
	    Scheduling.setCurrentGroupStartTime(0L);

	    // Make sure it is now unLocked. This should always be the case unless some
	    // abnormal ADMIN type TR has been done on the Group.. might be better to
	    // do a forceUnlock() on the Group.
	    if (group.isLocked()) {
		logger.log(1, CLASS, id, "exec",
			   "Group: "+groupPath.toString()+
			   " Could not unlock with specified key ["+key+"]");
		return error(TRANSACTION.GROUP_UPDATE_ERROR, 
				     "Group: "+groupPath.toString()+
				     " could not unlock with specified key ["+key+"]");
	    }
	    
	    logBuffer.append("\nOK unlocked:");
	    logBuffer.append("\nChecking type of Group: "+group.getClass().getName());
	    
	    // Check for the type of Group.
	    
	    if 
		(group instanceof MonitorGroup) {
		
		// --------------
		// MONITOR_GROUP.
		// --------------
		
		long period = ((MonitorGroup)group).getPeriod();
		long start  = ((MonitorGroup)group).getStartDate();
		
		int iPeriod = 
		    (int)Math.rint( (double)(doneTime - start) /
				    (double)period);
				    
		int cPeriod = 
		    (int)Math.rint( (double)(now - start) /
				    (double)period);
		if (success) {
		    ((MonitorGroup)group).setLastPeriod(iPeriod);
		    ((MonitorGroup)group).setLastCompleted(doneTime);
		}

		// ### This should be in SCHEUDLEImpl and should be the actual time not NOW !
		((MonitorGroup)group).setLastScheduled(doneTime);

		logBuffer.append("\nUpdate: MonitorGroup"+
				 "\n  Event:     "+(success ? "DONE" : "FAILED")+
				 "\n  At:        "+sdf.format(new Date(doneTime))+				
				 "\n  Started:   "+sdf.format(new Date(start))+
				 "\n  Period:    "+(period/1000.0)+" secs."+ 
				 "\n  Window info:"+
				 "\n   *Now:      "+cPeriod+
				 "\n   *Exec at:  "+iPeriod);
	    } else if
		(group instanceof EphemerisGroup) {

		// ----------------
		// EPHEMERIS_GROUP.
		// ----------------
			
		group.setDone(success);
		group.setDoneDate(doneTime);
		logBuffer.append("\nUpdate: EphemerisGroup");
	
	    } else if
		(group instanceof FixedGroup) {

		// ------------
		// FIXED_GROUP.
		// ------------

		group.setDone(success);
		group.setDoneDate(doneTime);
		logBuffer.append("\nUpdate: FixedGroup"+
				 "\n  Event:       "+(success ? "DONE" : "FAILED")+
				 "\n  At:          "+sdf.format(new Date(doneTime))+ 
				 "\n  *Fixed-time: "+sdf.format(new Date(((FixedGroup)group).getFixedTime())));
		if (success) {
		    Scheduling.removeFixedGroup((FixedGroup)group);
		    logBuffer.append("Update: Removed FixedGroup from FG-cache");
		}
				 				 
	    } else if
		(group instanceof RepeatableGroup) {

		// -----------------
		// REPEATABLE_GROUP.
		// -----------------

		if (success) {
		    // setup completion info.
		    ((RepeatableGroup)group).incCountCompleted();
		    ((RepeatableGroup)group).setLastCompleted(doneTime);
		}

		long start       = ((RepeatableGroup)group).getStartDate();
		long minInterval = ((RepeatableGroup)group).getMinimumInterval();
		int  maxRepeats  = ((RepeatableGroup)group).getMaximumRepeats();
		int  repeatCount = ((RepeatableGroup)group).getCountCompleted();
		
		logBuffer.append("\nUpdate: RepeatableGroup"+
				 "\n  Event:        "+(success ? "DONE" : "FAILED")+
				 "\n  At:           "+sdf.format(new Date(doneTime))+ 
				 "\n  Started:      "+sdf.format(new Date(start))+
				 "\n  Min-interval: "+(minInterval/1000.0)+" secs."+
				 "\n  Max-repeats:  "+maxRepeats+
				 "\n  *Repeat#:     "+repeatCount);

	    } else { 
		
		// -------------------------
		// GENERIC (FLEXIBLE) GROUP.
		// -------------------------
		
		group.setDone(success);
		group.setDoneDate(doneTime);
		logBuffer.append("\nUpdate: GenericGroup");
	    }
	    
	    // ALL GROUPS

	    // This is the one that counts..	  
	    group.updateHistory(doneTime, success, (int)usedTime, ""+code, details);
	    logBuffer.append("\nUpdated Group history.");

	    logBuffer.append("\nUpdating TAG and Proposal time allocations:");
	 
	    //   NOTE: We use the group's min required conditions NOT the actual ones which
	    //         may well be better.

	    //   1.  Update the Tag fields relating to the allocated fraction for the
	    //        Group's Tag. ie this Tag has had (+timeused under minconditions)..
	    //   2.  The Tag could get this be querying its user/props lists.??
	    //        Tag needs this for 'fairness function' next call to RCS_Sched. 
	    //   3.  Update the Proposal's usedFraction under the scheduled conditions..
	    
	    
	    // Locate the Proposal and Tag owners.
	    
	    // If the Group was one which specified Twilight and the TAG has a free
	    // twilight time allocation and it IS twilight then dont update the
	    // TAG and Proposal fractions. 
	    // ## 
	    // ## if (tag.hasFreeTwilightTime() and itIsTwilight -> SKIP
	    // ## 
	    // ## Also need to update FixedTime used in proposal if FG.

	    if (success) {
		logBuffer.append("\nSearching for TAG owner:");
	    
		Tag tag = OSS_Utilities.fetchTag(groupPath);
		
		// Is TAG there.
		if (tag == null) {
		    logger.log(1, CLASS, id, "exec",
			       "Tag: "+groupPath.getTagByName()+" Not in database.");
		    return error(TRANSACTION.GROUP_UPDATE_ERROR, 
					 "Could not find Tag: "+groupPath.getTagByName());
		}
		logBuffer.append("\nFetched Tag: "+tag.getName());
		
		// Group's nominal exec time millis convert to hours, not the actual time !
		Scheduling.calculateExecutionTimes(group);
		float timeUsed = (float)(group.getNominalExecutionTime()/3600000.0); 

		ChargingModel cm = Oss.getInstance().getChargingModel();
		if (cm != null) {
		    ExecutionCosting cost = cm.calculateCost(group, doneTime);
		    logBuffer.append("Charging model breakdown:"+cost);



		}

		// Requested minimum conditions.
		int   seeing   = group.getMinimumSeeing(); 
		int   lunar    = group.getMinimumLunar();
		
		logBuffer.append("\n\tNominal time used: "+(timeUsed*3600.0f)+" secs."+
				 "\n\tRequested Seeing:  "+Group.toSeeingString(seeing)+
				 "\n\tRequested Lunar:   "+Group.toLunarString(lunar));
		
	
		// Time accounting (hours)
		float tt = proposal.getUsedTime();
		proposal.setUsedTime(tt + timeUsed);	
		
		float at = tag.getTotalAchievedTime();   
		tag.setTotalAchievedTime(at + (float)(usedTime/3600000.0)); // check if ut is sec or msec
		
		// We have no category for crap seeing ! 
		if (seeing >= 0) {
		    float tu = proposal.getTimeUsed(lunar, seeing);	   
		   
		    double timeFraction =  proposal.getTimeUsed(lunar, seeing) /
			(proposal.getAllocatedFraction(lunar, seeing)
			 * proposal.getAllocatedTime());
		
		    logBuffer.append("\n\tCurrent Proposal time allocations:"+
				     "\n\t\tTotal time allocated: "+proposal.getAllocatedTime()+" hrs."+
				     "\n\t\t Under Conditions:    "+nf.format(100.0*proposal.getAllocatedFraction(lunar, seeing))+"%"+
				     "\n\t\tTotal time used:      "+tt+" hrs."+
				     "\n\t\t Under Conditions:    "+tu+" hrs."+
				     "\n\t\t Condition fraction:  "+nf.format(100.0*timeFraction)+"%");
		
		    proposal.setTimeUsed(lunar, seeing, tu + timeUsed); 
		    		    
		    timeFraction =  proposal.getTimeUsed(lunar, seeing) /
			(proposal.getAllocatedFraction(lunar, seeing)
			 * proposal.getAllocatedTime());
		    
		    logBuffer.append("\nUpdated Proposal time allocations:"+
				     "\n\tTotal time used:        "+proposal.getUsedTime()+" hrs."+
				     "\n\t Under conditions:      "+proposal.getTimeUsed(lunar, seeing)+" hrs."+
				     "\n\t Condition fraction:    "+nf.format(100.0*timeFraction)+"%");
		    
		    //logBuffer.append("\nNot updating Proposal fixed-fraction at present - TBD");
				    
		    float ta = tag.getAchievedTime(lunar, seeing);	   
		    
		    double fairnessFraction = tag.getAchievedTime(lunar, seeing) /
			(tag.getTargetFraction(lunar, seeing)
			 * tag.getTotalAllocatedTime());
		    
		    
		    logBuffer.append("\nCurrent Tag time allocations:"+
				     "\n\tTotal time allocated:   "+tag.getTotalAllocatedTime()+" hrs."+
				     "\n\t Target fraction:       "+
				     nf.format(100.0*tag.getTargetFraction(lunar, seeing))+"%"+
				     "\n\tTotal time acheived:    "+tag.getTotalAchievedTime()+" hrs."+
				     "\n\t In conditions:         "+tag.getAchievedTime(lunar, seeing)+" hrs."+
				     "\n\t Condition fraction:    "+nf.format(100.0*fairnessFraction)+"%");
		    
		    tag.setAchievedTime(lunar,seeing, ta + (float)((usedTime/3600000.0)));
		    
		    fairnessFraction = tag.getAchievedTime(lunar, seeing) /
			(tag.getTargetFraction(lunar, seeing)
			 * tag.getTotalAllocatedTime());
		    
		    logBuffer.append("\nUpdated Tag time allocations:"+
				     "\n\tTotal time acheived:    "+tag.getTotalAchievedTime()+" hrs."+
				     "\n\t In conditions:         "+tag.getAchievedTime(lunar, seeing)+" hrs."+
				     "\n\t Condition fraction:    "+nf.format(100.0*fairnessFraction)+"%)");
		}
		
	    } else {


		if (tabuTime > 0L) {		    
		    Scheduling.addTabuGroup(groupPath.toString(), now+tabuTime);
		    logBuffer.append("\nGroup posted onto tabulist for next: "+(tabuTime/1000)+" secs");
		}

	    }
	    
	    // Store it
	    try {
		OSS_Utilities.storeProposal(proposal);
	    } catch (IOException ix) {
		logger.log(1, CLASS, id, "exec",
			   "Proposal ["+proposal.getFullPath()+
			   "] Unable to Store: "+ix);
		return error(TRANSACTION.DATABASE_UPDATE_ERROR, 
			     "Proposal ["+proposal.getFullPath()+
			     "] Unable to Store: "+ix);
	    }
	    
	    logBuffer.append("\nSaved changes to database:");
	    
	    UPDATE_GROUP_DONE done = new UPDATE_GROUP_DONE(id);
	    done.setSuccessful(true);
	    return done;
	   
	} catch (Exception e) {
	    logger.log(1, CLASS, id, "exec",  "Error during transaction: " + e);
	    e.printStackTrace();
	    return error(TRANSACTION.DATABASE_READ_ERROR, 
				 "Error reading OSS Database: "+ e);
	} finally {
	    t.commit();
	    myLog.log(1, CLASS, id, "exec", logBuffer.toString());
	}
    }
}



